從昨天的流程圖可以發現,我們還必須將用戶要發佈的貼文資訊儲存到indexedDB中才行R。那我就接續昨天在feed.js中增加的code:
if('serviceWorker' in navigator && 'SyncManager' in window) {
navigator.serviceWorker.ready.then(function(sw) {
var post = {
id: new Date().toISOString(),
title: titleInput.value,
location: locationInput.value
};
writeData('sync-posts', post).then(function() {
return sw.sync.register('sync-new-post');
}).then(function() {
var snackbarContainer = document.querySelector('#confirmation-toast');
var data = {message: '貼文已經儲存並開始背景同步!'};
snackbarContainer.MaterialSnackbar,showSnackbar(data);
}).catch(function(err) {
console.log(err);
});
});
} else {
sendData();
}
我先將用戶貼文資料打包成javascript object存到post變數裡,接下來還記得我之前在utility.js中已經完成了「寫入資料到indexedDB (writeData)」的funciton,這裡就可以直接使用這個函式啦。
在成功寫入indexedDB後,我才要執行註冊同步工作。因為之後service worker要從indexedDB中把POST的資料傳送出去,所以必須得確定資料已經存在indexedDB了。
最後的snackbarContainer是我直接使用material design提供的components,來增加user experience。當以上步驟都就緒後,會在視窗底部跳出一個通知,讓用戶知道「貼文已經儲存並開始背景同步!」。
接下來如果今天瀏覽器不支援Background Synchronous哩?這樣是不是需要一個Fallback的方法,能直接將用戶的貼文資料傳送到我們的server裡(目前因為還沒實作server,所以這裡指的是後台firebase資料庫),這裡我又實作另一個sendData() function:
function sendData() {
fetch('https://trip-diary-f56de.firebaseio.com/posts.json', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
id: new Date().toISOString,
title: titleInput.value,
location: locationInput.value,
image: 'https://firebasestorage.googleapis.com/v0/b/trip-diary-f56de.appspot.com/o/sf-boat.jpg?alt=media&token=fde987b2-5213-4d0e-b4ff-d3a548dc107e'
})
}).then(function(res) {
console.log('Send data', res);
updateUI();
})
}
簡單說明一下我的邏輯,一樣使用fetch API發出POST Request並設定表頭,另外在Request的body中,因為我還沒實作拍照的功能,所以image欄位就先寫死。
最後重點來了~~ 要在service worker中加入監聽sync event了。這樣當網路恢復連線時,service worker就知道要將儲存在indexedDB中待處理的貼文資料POST出去:
self.addEventListener('sync', function(event) {
console.log('[Service Worker] Background syncing', event);
if(event.tag === 'sync-new-posts。是的話就可以開始') {
console.log('[Service Worker] Syncing new Posts');
event.waitUntil(
readAllData('sync-posts').then(function(data) {
for(var dt of data) {
// feed.js中sendData()的code
fetch('https://trip-diary-f56de.firebaseio.com/posts.json', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
id: dt.id,
title: dt.title,
location: dt.location,
image: 'https://firebasestorage.googleapis.com/v0/b/trip-diary-f56de.appspot.com/o/sf-boat.jpg?alt=media&token=fde987b2-5213-4d0e-b4ff-d3a548dc107e'
})
}).then(function(res) {
console.log('Send data', res);
if(res.ok) {
deleteItemFromData('sync-posts', dt.id);
}
}).catch(function(err) {
console.log('Error while sending data', err);
});
}
})
);
}
});
由於可能會有多個不同的同步工作要進行不同的處理,所以首先我們要先確定監聽到的「event tag」是否等於「我當初在註冊同步工作時設定的id」,這裡是sync-new-posts。是的話就跟之前一樣把我們接下去要執行的邏輯放在event.waitUntil()中,原因是為了避免執行同步工作時,其它的event listener又要執行其他的工作而發生衝突。
接著我在utility.js中也已經寫好讀取indexedDB的function了(readAllData),這裡當然直接使用囉。回傳回來的是一個陣列,所以我用一個for-of loop將每一筆的貼文「去執行之前在feed.js中寫好的sendData() code」,也就是傳送到後台firebase資料庫中。
在該筆貼文傳送完成後,如果response為200 OK,那就要來清除「sync-new-posts」這個object store中的這一則貼文,不過我好像還沒在utility.js中實作這個function,來看一下要怎麼寫吧:
function deleteItemFromData(objectStore, id) {
dbPromise.then(function(db) {
var tx = db.transaction(objectStore, 'readwrite');
var store = tx.objectStore(objectStore);
store.delete(id);
return tx.complete;
}).then(function() {
console.log('Item deleted!');
});
}
基本上步驟都跟其他操作indexedDB的函式差不多,只是要刪除某個object store中的單筆資料,必須要使用delete(id)這個function才行,當然這裡的id是我存在indexedDB中的每一則貼文的「時間」。
Day20 結束!!